﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Net;
using System.Runtime.Serialization;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Microsoft.Phone.Shell;
using System.Collections.ObjectModel;

namespace TodayHumor
{
    [DataContract]
    public class Article
    {
        [DataMember]
        public string Title { get; set; } // 글의 제목
        [DataMember]
        public string Board { get; set; } // 글이 속해 있는 게시판 ID
        [DataMember]
        public string Href { get; set; } // 글 링크
        [DataMember]
        public string Writer { get; set; } // 글 작성자
        [DataMember]
        public string Like { get; set; } // 글 추천/반대
        [DataMember]
        public string Comments { get; set; } // 글 댓글 수
        [DataMember]
        public string LikeLink { get; set; } // 글을 추천/반대할 링크

        public Article(string title, string board, string href, string writer)
        {
            this.Title = title;
            this.Board = board;
            this.Href = href;
            this.Writer = writer;
        }

        public Article(string title, string board, string href, string writer, string like, string comments)
        {
            this.Title = title;
            this.Board = board;
            this.Href = href;
            this.Writer = writer;
            this.Like = like;
            this.Comments = comments;
        }

        public static bool operator ==(Article a, Article b)
        {
            // 두 개 모두가 null이거나 a, b가 똑같은 개체이면 true를 반환한다.
            if (System.Object.ReferenceEquals(a, b))
            {
                return true;
            }

            // 하나가 null이고 나머지가 null이 아닐 때에는 false를 반환한다.
            if (((object)a == null) || ((object)b == null))
            {
                return false;
            }

            // 글 링크가 같으면 똑같은 글이므로, 글 링크가 같으면 true를 반환한다.
            return a.Href == b.Href;
        }

        public static bool operator !=(Article a, Article b)
        {
            return !(a == b);
        }

        public override int GetHashCode()
        {
            unchecked // 오버플로우 가능성
            {
                int hash = 17;
                hash = hash * 23 + Title.GetHashCode();
                hash = hash * 23 + Writer.GetHashCode();
                hash = hash * 23 + Href.GetHashCode();
                return hash;
            }
        }
    }

    public class ArticleLoader
    {
        Article article;
        int LastCommentNo;

        public ArticleLoader(Article Article)
        {
            this.article = Article;
        }

        public async Task<List<ArticlePart>> LoadArticle()
        {
            RaonWebClient WebClient = new RaonWebClient();

            string Uri = "http://m.todayhumor.co.kr/" + article.Href;
            string Result = await WebClient.DownloadStringTaskAsync(new Uri(Uri));

            // 다운로드받은 게시글을 파싱한 List를 반환한다.
            return await ParseArticle(Result);
        }

        async Task<List<ArticlePart>> ParseArticle(string result)
        {
            List<ArticlePart> ArticlePartList = new List<ArticlePart>(); // 반환할 게시글 조각 리스트

            // 정규 표현식
            Regex TitleRegex        = new Regex("(?<=<span class=\"view_subject\">)(.|\n)*?(?=</span>)");
            Regex WriterRegex       = new Regex("(?<=<span class=\"view_writer\" is_member=\".*?\">).*?(?=</span>)");
            Regex CommentsRegex     = new Regex("(?<=<span class=\"view_replyCount\">).*?(?=</span>)");
            Regex DateRegex         = new Regex("(?<=<span class=\"view_(bestReg|w)Date\" >).*?(?=</span>)");
            Regex ContentWrapRegex  = new Regex("(?<=<div class=\"view_content\" >)(.|\n)*?(?=</div>(.|\n)*?function board_delete)");
            Regex ImageRegex        = new Regex("(?<=<img.*?src=\").*?(?=\".*?>)");
            Regex UriRegex          = new Regex("https?:\\/\\/[-\\w.]+(:\\d+)?(\\/([\\w\\/_.?=&]*)?)?");
            Regex LikeLinkRegex     = new Regex("(?<=var ok_flag = \").*?(?=\")");
            Regex BoardRegex        = new Regex("(?<=var table = \")(.|\n)*?(?=\")");
            Regex LikeDislikeRegex  = new Regex("(?<=<span class=\"view_okNok\">)(.|\n)*?(?=</span>)");
            Regex MobileRegex       = new Regex("<m>");

            Regex ArticleCommentIdRegex = new Regex("(?<=var table_memo_no = \").*?(?=\";)");
            Regex ArticleIdRegex        = new Regex("(?<=var no = \").*?(?=\";)");
            Regex LastCommentIdRegex    = new Regex("(?<=lastMemoNumber = ).*?(?=</)");
            Regex ArticleTableRegex     = new Regex("(?<=var table = \").*?(?=\";)");

            Match TitleMatch        = TitleRegex.Match(result);
            Match WriterLikeMatch   = WriterRegex.Match(result);
            Match CommentsMatch     = CommentsRegex.Match(result);
            Match BoardMatch        = BoardRegex.Match(result);
            Match LikeDislikeMatch  = LikeDislikeRegex.Match(result);

            MatchCollection DateMatches = DateRegex.Matches(result);

            // 게시글의 제목 부분을 파싱해서
            string Title = HttpUtility.HtmlDecode(Regex.Replace(TitleMatch.Value, "<.*?>", "").Trim());
            string Category = "Images/BoardIcons/" + BoardMatch.Value + ".png";
            string Writer = WriterLikeMatch.Value;
            string LikeDislike = LikeDislikeMatch.Value;
            string Date = DateMatches[0].Value;
            string Comments = CommentsMatch.Value;
            string Mobile = MobileRegex.Match(Title).Value == "<m>" ? "모바일" : "";

            // 게시글 부분을 모아놓는 List에 넣는다.
            ArticlePartList.Add(new ArticlePart(Title.Replace("<m>", ""), Category, Writer, LikeDislike, Mobile));

            // 게시글의 추천 링크도 Article에 추가한다.
            string ArticleLikeLink = LikeLinkRegex.Match(result).Value;

            // 내용을 파싱한다.
            string ContentWrap = ContentWrapRegex.Match(result).Value;

            // 정규표현식으로 br, p, div 등의 태그를 경계로 Split하여 각각 파싱한다.
            foreach (string paragraph in Regex.Split(ContentWrap, "(<br.*?>)|(<p.*?>)|(<div.*?>|<li.*?>)", RegexOptions.IgnoreCase))
            {
                string Content = paragraph;

                // 이미지(들)이 있을 경우, 먼저 처리하여 게시글 부분을 모아넣는 List에 추가하고,
                MatchCollection ImageUriMatches = ImageRegex.Matches(Content);
                foreach (Match ImageUriMatch in ImageUriMatches)
                {
                    string ImageUriString = ImageUriMatch.Value;
                    // Uri가 비어있는 경우 대비
                    if (!String.IsNullOrEmpty(ImageUriString))
                    {
                        ArticlePartList.Add(new ArticlePart(new Uri(ImageUriString), ArticlePart.Type.Image));
                    }
                }

                // 그 후 내용을,
                string StringContent = Regex.Replace(Content, "(<.*?>|https?:\\/\\/[-\\w.]+(:\\d+)?(\\/([\\w\\/_.?=&]*)?)?)", "");
                string StringContentTrimmed = HttpUtility.HtmlDecode(StringContent).Trim();
                if (!String.IsNullOrEmpty(StringContentTrimmed))
                {
                    ArticlePartList.Add(new ArticlePart(StringContentTrimmed));
                }

                // 그 후 링크를 추가한다.
                // a href나 이미지가 있을 경우 링크가 중복되어 나오므로
                Content = Regex.Replace(Content, "<(img|a).*?>", "");

                MatchCollection LinkUriMatches = UriRegex.Matches(Content);
                foreach (Match LinkUriMatch in LinkUriMatches)
                {
                    string LinkUriString = LinkUriMatch.Value;
                    if (!String.IsNullOrEmpty(LinkUriString))
                    {
                        ArticlePartList.Add(new ArticlePart(new Uri(LinkUriString), ArticlePart.Type.Link));
                    }
                }
            }

            // 내용이 끝났으니 작성일과 댓글 수를 알려주는 게시글 부분을 추가한다.
            ArticlePartList.Add(new ArticlePart(Comments, Date));

            // 댓글 작업 시작

            // 댓글이 50개가 넘어가면 50개로 잘리므로,
            // 50개가 넘어가는 경우 추가 작업을 해 주어야 한다.
            // ParseCommentsData는 댓글이 50개보다 더 많으면 true, 적으면 false를 반환한다.

            // 댓글이 50개보다 많은 경우
            if (ParseCommentsData(result, ref ArticlePartList))
            {
                // 마지막 댓글 번호를 찾아서
                MatchCollection LastCommentIdMatches = LastCommentIdRegex.Matches(result);

                // 댓글을 더 받아올 때만 사용되는 값들을 추출해낸 후
                string ArticleCommentId = ArticleCommentIdRegex.Match(result).Value;
                string ArticleId = ArticleIdRegex.Match(result).Value;
                string LastCommentId = LastCommentIdMatches[LastCommentIdMatches.Count - 1].Value;
                string ArticleTable = ArticleTableRegex.Match(result).Value;

                // 댓글을 받아온다.
                RaonWebClient WebClient = new RaonWebClient();

                string CommentUri = "http://m.todayhumor.co.kr/" + String.Format("ajax_memo.php?table_memo_no={0}&parent_id={1}&last_memo_num={2}&memo_count_limit=10000&table={3}&no={1}&flag={4}",
                                                                   ArticleCommentId, ArticleId, LastCommentId, ArticleTable, new Random().Next());
                string CommentResult = await WebClient.DownloadStringTaskAsync(new Uri(CommentUri));

                ParseCommentsData(CommentResult, ref ArticlePartList);

                // 마지막으로 댓글 창을 추가한다.
                ArticlePartList.Add(new ArticlePart());
            }
            else
            {
                // 마지막으로 댓글 창을 추가한다.
                ArticlePartList.Add(new ArticlePart());
            }

            return ArticlePartList;
        }

        bool ParseCommentsData(string TargetString, ref List<ArticlePart> ArticlePartList)
        {
            Regex CommentRegex = new Regex("<div.*?class=\"reply_unit\".*?>(.|\n)*?</div>(.|\n)*?(<script>|<!--추가댓글자리-->)");
            Regex CommentWriterRegex = new Regex("(?<=<font color=black>)(.|\n)*?(?=</font>)");
            Regex CommentLikeRegex = new Regex("(?<=<span class=\"memo_ok_nok\".*?>)(.|\n)*?(?=</span>)");
            Regex CommentContentRegex = new Regex("(?<=<div class=\"memo_content.*?\".*?>)(.|\n)*?(?=</div>(.|\n)*</div>)");
            Regex CommentDateRegex = new Regex("(?<=<span class=\"memo_date\">)(.|\n)*?(?=</span>)");
            Regex CommentFlagRegex = new Regex("(?<=memo_ok_click\\(this,)(.|\n)*?(?=,'memo_ok)");
            Regex CommentIsMoreRegex = new Regex("<input type=\"button\" value=\"댓글 50개 더 보기\"(.|\n)*?>");
            Regex ImageRegex = new Regex("(?<=<img.*?src=\").*?(?=\".*?>)");
            Regex UriRegex = new Regex("https?:\\/\\/[-\\w.]+(:\\d+)?(\\/([\\w\\/_.?=&]*)?)?");
            Regex LastMemoNumberRegex = new Regex("(?<=lastMemoNumber \\= ).*?(?=</script>)");

            MatchCollection CommentMatches = CommentRegex.Matches(TargetString);
            bool CommentIsMore = CommentIsMoreRegex.Match(TargetString).Value != "";

            foreach (Match CommentMatch in CommentMatches)
            {
                string Comment = CommentMatch.Value;
                string CommentWriter = CommentWriterRegex.Match(Comment).Value;
                string CommentLikeDislike = CommentLikeRegex.Match(Comment).Value;
                string CommentContent = CommentContentRegex.Match(Comment).Value.Trim();
                string CommentDate = CommentDateRegex.Match(Comment).Value;
                string[] CommentFlagInfo = CommentFlagRegex.Match(Comment).Value.Replace("'", "").Split(',');

                string CommentFlag = String.Format("?ok_flag={0}&no={1}&tmn={2}", article.LikeLink, CommentFlagInfo[1], CommentFlagInfo[0]);
                ArticlePartList.Add(new ArticlePart(CommentWriter, CommentLikeDislike, CommentFlag, CommentDate));

                foreach (string paragraph in Regex.Split(CommentContent, "(<br.*?>)|(<p.*?>)|(<div.*?>|<li.*?>)", RegexOptions.IgnoreCase))
                {
                    string Content = paragraph;

                    MatchCollection ImageUriMatches = ImageRegex.Matches(Content);
                    foreach (Match ImageUriMatch in ImageUriMatches)
                    {
                        string ImageUriString = ImageUriMatch.Value;
                        if (!String.IsNullOrEmpty(ImageUriString))
                        {
                            ArticlePartList.Add(new ArticlePart(new Uri(ImageUriString), ArticlePart.Type.Image));
                        }
                    }

                    string StringContent = Regex.Replace(Content, "(<.*?>|https?:\\/\\/[-\\w.]+(:\\d+)?(\\/([\\w\\/_.?=&]*)?)?)", "").Replace("\n", "");

                    if (!String.IsNullOrEmpty(HttpUtility.HtmlDecode(StringContent).Trim()))
                    {
                        ArticlePartList.Add(new ArticlePart(HttpUtility.HtmlDecode(StringContent).Trim(), CommentFlag, true));
                    }

                    Content = Regex.Replace(Content, "<(img|a).*?>", "");

                    MatchCollection LinkUriMatches = UriRegex.Matches(Content);
                    foreach (Match LinkUriMatch in LinkUriMatches)
                    {
                        string LinkUriString = LinkUriMatch.Value;
                        if (!String.IsNullOrEmpty(LinkUriString))
                        {
                            ArticlePartList.Add(new ArticlePart(new Uri(LinkUriString), ArticlePart.Type.Link));
                        }
                    }
                }
            }
            
            MatchCollection LastMemoNumberMatches = LastMemoNumberRegex.Matches(TargetString);
            LastCommentNo = Int32.Parse(LastMemoNumberMatches[LastMemoNumberMatches.Count - 1].Value);

            return CommentIsMore;
        }
    }

    [DataContract]
    public class ArticlePart
    {
        public enum Type
        {
            Title, Content, CommentsAndDate,
            Comment, CommentContent, Write,
            Link, Image, AnimatedImage
        }

        /// <summary>
        /// 글의 제목 부분을 만듭니다.
        /// </summary>
        /// <param name="title">글의 제목입니다.</param>
        /// <param name="category">유머글, 시사 등의 카테고리 이미지 Uri입니다.</param>
        /// <param name="date">글이 언제 베스트오브베스트에 올라갔는지, 또는 언제 작성되었는지를 말해주는 문자열입니다.</param>
        /// <param name="likedislike">추천/비추천입니다.</param>
        /// <param name="comments">댓글의 수입니다.</param>
        /// <param name="mobile">모바일인지 아닌지를 판가름합니다.</param>
        public ArticlePart(string title, string category, string writer, string likedislike, string mobile)
        {
            ArticlePartType = Type.Title; 

            Title = title; 
            Category = category; 
            Writer = writer; 
            LikeDislike = likedislike; 
            Mobile = mobile;
        }

        /// <summary>
        /// 본문 부분입니다.
        /// </summary>
        /// <param name="content">본문 문자열입니다.</param>
        public ArticlePart(string content)
        {
            ArticlePartType = Type.Content; 
            
            Content = content;
        }

        public ArticlePart(string comments, string date)
        {
            ArticlePartType = Type.CommentsAndDate; 
            
            Comments = comments; 
            Date = date;
        }

        /// <summary>
        /// 이미지 또는 링크 부분입니다. 뒤의 bool이 true이면 링크, false이면 이미지 Uri를 나타냅니다.
        /// </summary>
        /// <param name="link">이미지 또는 링크의 Uri입니다.</param>
        /// <param name="isLink">이 생성자가 링크를 위한 것인지 이미지 Uri를 위한 것인지를 판별하는 bool입니다.</param>
        public ArticlePart(Uri link, Type type)
        {
            switch (type)
            {
                case Type.Link:
                    ArticlePartType = Type.Link;
                    Link = link;
                    break;
                case Type.Image:
                    ArticlePartType = Type.Image;
                    PicUri = link;
                    break;
                case Type.AnimatedImage:
                    ArticlePartType = Type.AnimatedImage;
                    PicUri = link;
                    break;
            }
        }

        //public ArticlePart(Uri link, bool isLink)
        //{
        //    if (isLink)
        //    {
        //        Type = "Link"; Link = link;
        //    }
        //    else
        //    {
        //        Type = "Image"; PicUri = link;
        //    }
        //}

        /// <summary>
        /// 댓글의 작성자와 추천/반대를 표시하는 부분입니다.
        /// </summary>
        /// <param name="writer">댓글 작성자의 닉네임입니다.</param>
        /// <param name="likedislike">댓글 추천/비추천 수입니다.</param>
        /// <param name="flag">댓글의 추천/반대 링크입니다.</param>
        /// <param name="date">댓글이 작성된 일시입니다.</param>
        public ArticlePart(string writer, string likedislike, string flag, string date)
        {
            ArticlePartType = Type.Comment; CommentWriter = writer; CommentLikeDislike = likedislike; Flag = flag; CommentDate = date;
        }

        /// <summary>
        /// 댓글의 내용을 표시하는 부분입니다.
        /// </summary>
        /// <param name="CommentContent">댓글의 내용 중 한 줄입니다.</param>
        /// <param name="CommentFlag">댓글의 추천/반대 링크입니다.</param>
        /// <param name="dummy">생성자를 구분하기 위한 dummy입니다.</param>
        public ArticlePart(string CommentContent, string CommentFlag, bool dummy)
        {
            ArticlePartType = Type.CommentContent; Content = CommentContent; Flag = CommentFlag;
        }

        /// <summary>
        /// 댓글 입력 TextBox입니다.
        /// </summary>
        public ArticlePart()
        {
            ArticlePartType = Type.Write;
        }

        // 공통
        [DataMember]
        public Type ArticlePartType { get; set; }

        // 제목 부분
        [DataMember]
        public string Title { get; set; }
        [DataMember]
        public string Category { get; set; }
        [DataMember]
        public string Writer { get; set; }
        [DataMember]
        public string Date { get; set; }
        [DataMember]
        public string LikeDislike { get; set; }
        [DataMember]
        public string Comments { get; set; }
        [DataMember]
        public string Mobile { get; set; }

        // 본문 부분
        [DataMember]
        public string Content { get; set; }

        // 링크 부분
        [DataMember]
        public Uri Link { get; set; }
        [DataMember]
        public string LinkContent
        {
            get { return Link.Host; }
        }

        // 이미지 부분
        [DataMember]
        public Uri PicUri { get; set; }
        [DataMember]
        public bool ImageLoaded { get; set; }

        // 댓글 부분
        [DataMember]
        public string CommentWriter { get; set; }
        [DataMember]
        public string Comment { get; set; }
        [DataMember]
        public string CommentLikeDislike { get; set; }
        [DataMember]
        public string Flag { get; set; }
        [DataMember]
        public string CommentDate { get; set; }
    }

    public class HistoryLoader
    {
        public ObservableCollection<Article> Articles = null;

        string HistorySavePath = "/History.txt";

        public void Load()
        {
            // Articles를 초기화해 주고
            Articles = new ObservableCollection<Article>();

            // IsolatedStorage를 얻어와서
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();

            // 열람 기록이 기록된 파일이 있는지 검사한다.
            if (storage.FileExists(HistorySavePath))
            {
                // 있으면 파싱한다. (제목 / 게시글이 속해 있는 게시판 / 링크 / 작성자 순으로 \t으로 구분됨)
                using (StreamReader BoardReader = new StreamReader(storage.OpenFile(HistorySavePath, FileMode.Open)))
                {
                    string ReadLine;
                    while ((ReadLine = BoardReader.ReadLine()) != null)
                    {
                        string[] ParsedString = ReadLine.Split('\t');

                        Articles.Add(new Article(ParsedString[0], ParsedString[1], ParsedString[2], ParsedString[3]));
                    }
                }
            }
        }

        public void Add(Article article)
        {
            // 중복되는 Article을 모두 제거하고 (List에 저장 후 제거하는 이유 : InvalidOperationException 발생)
            List<Article> MatchArticles = new List<Article>();

            foreach (Article Article in Articles)
            {
                if (article == Article)
                {
                    MatchArticles.Add(Article);
                }
            }

            foreach (Article Article in MatchArticles)
            {
                Articles.Remove(Article);
            }

            // Index 0에 Insert한다.
            Articles.Insert(0, article);
            Save();
        }

        public void Remove(Article article)
        {
            Articles.Remove(article);
            Save();
        }

        public void Save()
        {
            // IsolatedStorage를 얻어와서
            IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();

            // BoardList.txt를 저장한다.
            using (StreamWriter HistoryWriter = new StreamWriter(storage.OpenFile(HistorySavePath, FileMode.Create)))
            {
                foreach (Article article in Articles)
                {
                    HistoryWriter.WriteLine(String.Format("{0}\t{1}\t{2}\t{3}", article.Title, article.Board, article.Href, article.Writer));
                }
            }
        }
    }
}